from random import randrange, choice, shuffle

import libtcodpy as T

# from corpse import Corpse
from mob import Monster, UnrealMonster, Player, Boss
from item import Item
from mapgen import generate_map
from settings import *
from util import *
import corpse
import ui

class Map(object):
    def __init__(self, level):
        self.tiles = generate_map(level)
        self.level = level

        self.player = None
        self.mobs = []

        self.fov_map = T.map_new(MAP_W, MAP_H)
        for x in range(MAP_W):
            for y in range(MAP_H):
                tile = self.tiles[x][y]
                T.map_set_properties(self.fov_map,
                                           x, y,
                                           tile.transparent,
                                           tile.walkable)

        self.populate()
        if self.level == MAX_DLEVEL:
            self.place_monsters(Boss)

    def __del__(self):
        T.map_delete(self.fov_map)

    def find_tile(self, func):
        for x in range(MAP_W):
            for y in range(MAP_H):
                tile = self.tiles[x][y]
                if func(tile):
                    return (x, y, tile)

    def recalc_fov(self):
        T.map_compute_fov(self.fov_map,
                                self.player.x, self.player.y,
                                MAP_W,
                                True)
        for x in range(MAP_W):
            for y in range(MAP_H):
                if self.is_visible(x, y):
                    self.tiles[x][y].remember_glyph()

    def is_visible(self, x, y):
        return T.map_is_in_fov(self.fov_map, x, y) and \
            distance(x, y, self.player.x, self.player.y) <= \
            self.player.fov_range

    def neighbor_tiles(self, x, y):
        for dx, dy in ALL_DIRS:
            if in_map(x+dx, y+dy):
                yield self.tiles[x+dx][y+dy]

    def __del__(self):
        T.map_delete(self.fov_map)

    def do_turn(self, t):
        for mob in self.mobs:
            mob.heartbeat()
            if mob.speed < 0 and \
                    t%(6+max(mob.speed, MIN_SPEED)) == 0:
                continue
            mob.act()
            if mob.speed > 0 and \
                    t%(6-min(mob.speed, MAX_SPEED)) == 0:
                mob.act()

    def populate(self):
        n_monsters = 3 + roll(2, self.level)
        n_items = roll(2, 4, 1)
        n_corpses = roll(self.level, 6)
        for i in range(n_monsters):
            mcls = random_by_level(self.level, Monster.ALL)
            self.place_monsters(mcls)
        for i in range(n_items):
            x, y, tile = self.random_empty_tile(no_mob=False, no_stair=True)
            item = random_by_level(self.level, Item.ALL)()
            tile.items.append(item)
        for i in range(n_corpses):
            mon = random_by_level(self.level, Monster.ALL)
            # Roll for older corpses deeper down
            cps_age = roll(2 * self.level, 20)
            x, y, tile = self.random_empty_tile(no_mob=False, no_stair=True)
            cps = corpse.Corpse(src_mon = mon, age = cps_age)
            tile.corpses.append(cps)

    def flood(self, x, y, mcls, n):
        if n == 0:
            return n
        if x < 0 or x >= MAP_W or y < 0 or y >= MAP_H:
            return n
        tile = self.tiles[x][y]
        if tile.mob or not tile.walkable:
            return n
        mcls().put(self, x, y)
        n -= 1
        dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)]
        shuffle(dirs)
        for dx, dy in dirs:
            n = self.flood(x+dx, y+dy, mcls, n)
        return n

    def place_monsters(self, mcls, *args, **kwargs):
        x, y, tile = self.random_empty_tile(*args, **kwargs)
        self.flood(x, y, mcls, mcls.multi)

    def random_empty_tile(self, no_mob=True, not_seen=False, no_stair=False):
        while True:
            x, y = randrange(MAP_W), randrange(MAP_H)
            tile = self.tiles[x][y]
            if not tile.walkable:
                continue
            if no_mob and tile.mob:
                continue
            if not_seen and self.is_visible(x, y):
                continue
            if no_stair and isinstance(tile, StairDownTile):
                continue
            return (x, y, tile)

    def flip_playfield_vert(self):
        """
        Flips the map up-and-down.

        a...b    c...d
        ..... -> .....
        c...d    a...b
        """
        new_tiles = array(MAP_W, MAP_H, lambda: ' ')
        for x in range(MAP_W):
            # ui.message('attempting x = %d' % x)
            for y in range(MAP_H):
                # ui.message('attempting y = %d' % (MAP_H - y -1))
                new_tiles[x][y] = self.tiles[x][MAP_H - y - 1]
                T.map_set_properties(self.fov_map,
                                        x, y,
                                        new_tiles[x][y].transparent,
                                        new_tiles[x][y].walkable)
        for mon in self.mobs:
            mon.y = MAP_H - mon.y - 1
        self.tiles = new_tiles
        self.recalc_fov()
        ui.draw_all()

    def flip_playfield_horiz(self):
        """
        Flips the map right-to-left.

        a...b    b...a
        ..... -> .....
        c...d    d...c
        """
        new_tiles = array(MAP_W, MAP_H, lambda: ' ')
        for x in range(MAP_W):
            for y in range(MAP_H):
                new_tiles[x][y] = self.tiles[MAP_W - x -1][y]
                T.map_set_properties(self.fov_map,
                                        x, y,
                                        new_tiles[x][y].transparent,
                                        new_tiles[x][y].walkable)
        for mon in self.mobs:
            mon.x = MAP_W - mon.x - 1
        self.tiles = new_tiles
        self.recalc_fov()
        ui.draw_all()

    def invert_playfield(self):
        """
        Flips the map across X and Y axes:

        a...b    d...c
        ..... -> .....
        c...d    b...a
        """
        new_tiles = array(MAP_W, MAP_H, lambda: ' ')
        for x in range(MAP_W):
            for y in range(MAP_H):
                new_tiles[x][y] = self.tiles[MAP_W - x - 1][MAP_H - y - 1]
                T.map_set_properties(self.fov_map,
                                        x, y,
                                        new_tiles[x][y].transparent,
                                        new_tiles[x][y].walkable)
        for mon in self.mobs:
            mon.x = MAP_W - mon.x - 1
            mon.y = MAP_H - mon.y - 1
        self.tiles = new_tiles
        self.recalc_fov()
        ui.draw_all()

    def flip_playfield_main(self):
        """
        Flips the map along the main diagonal (top left to bottom right).
        Not currently feasible under nonsquare maps.

        a...b    a...c
        ..... -> .....
        c...d    b...d
        """
        pass

    def flip_playfield_minor(self):
        """
        Flips the map along the minor diagonal (top right to bottom left).
        Not currently feasible under nonsquare maps.

        a...b    d...b
        ..... -> .....
        c...d    c...a
        """
        pass

    def rotate_playfield_cw(self):
        """
        Rotates the map clockwise.
        Not currently feasible under nonsquare maps.

        a...b    c...a
        ..... -> .....
        c...d    d...b
        """
        pass

    def rotate_playfield_ccw(self): 
        """
        Rotates the map counterclockwise.
        Not currently feasible under nonsquare maps.

        a...b    b...d
        ..... -> .....
        c...d    a...c
        """
        pass

class Tile(object):
    walkable = True
    transparent = True
    glyph = UNKNOWN_GLYPH
    known_glyph = ' ', T.white

    def __init__(self):
        self.mob = None
        self.items = []
        self.corpses = []

    @property
    def visible_glyph(self):
        if self.mob:
            return self.mob.glyph
        elif self.items:
            return self.items[-1].glyph
        elif self.corpses:
            return self.corpses[-1].glyph
        else:
            return self.glyph

    def remember_glyph(self):
        if self.items:
            self.known_glyph = self.items[-1].glyph
        else:
            self.known_glyph = self.glyph

    def forget_glyph(self):
        self.known_glyph = ' ', T.white

    def on_enter(self):
        pass

class FloorTile(Tile):
    name = 'floor'
    walkable = True
    transparent = True
    glyph = '.', T.grey

class WallTile(Tile):
    name = 'stone wall'
    walkable = False
    transparent = False
    glyph = '#', T.grey

class WoodWallTile(Tile):
    name = 'wooden wall'
    walkable = False
    transparent = False
    glyph = '#', T.dark_orange

class StairUpTile(Tile):
    name = 'stairs up'
    walkable = True
    transparent = True
    glyph = '<', T.light_grey

class StairDownTile(Tile):
    name = 'stairs down'
    walkable = True
    transparent = True
    glyph = '>', T.light_grey

    def on_enter(self):
        ui.message('There is a down stairway here.')
